Borobongo/stabilizing v2#367
Merged
BoroBongo merged 19 commits intoJun 13, 2026
Merged
Conversation
…, use hero Index for distance check Previously oldOther was incorrectly assigned from GlobalVictim instead of GlobalOther, causing both globals to be restored to the same (victim) value after every perception call. Also replaces the fragile npc2.Id==0 hero check with an Index comparison against GlobalHero.
Filter was comparing npcVob.CurrentStateIndex (the calling NPC's index) against each candidate's state, instead of comparing the requested aiState value.
…, skip AI_Attack for humans - Death: clear queue + StopAllAnimations then play directly so dying always wins - Hit: overlay hurt anim without interrupting current action - OnHit: early-out if target already dead to prevent double damage/animation - Attack: skip with log for human NPCs (guild < GIL_SEPERATOR_HUM), not yet implemented
…ted-out ClearState block
… spaces
Split(' ') on strings like '5 ' produces an empty token; added Where filter
to skip empty entries before Convert.ToInt32 on OptimalFrame, HitEnd, ComboWindow.
Iterates all string-type Daedalus symbols and collects unique characters, then calls TryAddCharacters to bake them into the atlas before any UI renders. Prevents TMP fallback glyphs on first display of Gothic NPC/item names.
…rs always fall through to sprite asset
TMP's GetTextElement() checks the font asset before the sprite asset. In Dynamic
mode the empty Liberation Sans atlas was being populated with Gothic characters on
first render, causing them to render in Liberation Sans instead of the Gothic bitmap
font. Switching to Static prevents any atlas population so all characters fall
through to the Gothic TMP_SpriteAsset set by AbstractMenu/dialogs.
Also cleared the placeholder texts ("x/y", "{{CATEGORY}}") in BackPack.prefab; they
were triggering TMP warnings before boot because they rendered before FontService set
the default sprite asset.
…t clamped to 1 ExtNpcHasItems was reading from GetItem/ItemCount (VOB world-file list) which is never populated by runtime CreateInvItems calls. Rewrote to iterate all InvCats and read from GetPacked via GetInventoryItems — the same path used by all other inventory operations. VRBackpack was passing IItem.Amount directly to AddItem/RemoveItem. In ZenKit, world VOB items have Amount=0 (Gothic convention: single item = amount 0), so items picked up from the floor were stored as e.g. ITFOBEERx0 in packed inventory. Applied Mathf.Max(1, amount) to all four socket methods to treat 0 as 1. Fixes Npc_HasItems always returning 0 for player inventory, breaking dialogs that check whether the player has items (Fisk ore purchase, Bloodwyn extortion, Drax beer, etc.).
…Sound in GetSoundClip InitVobCoroutine now wraps each InitVob call in try/catch so a single broken VOB can't halt all lazy loading. Root cause was BIRD/OWL NEAR MARKET referencing wood_night2 which doesn't exist in Gothic's SfxInst.d — GetFirstSound() returned null and silently killed the coroutine. SfxModel now logs a warning when an SFX symbol is missing from the VM.
HVR sets isKinematic=false on the grabbed Rigidbody during the grab, which caused the corpse to be affected by gravity and pushable by the player capsule after releasing. ForceRelease + DisablePhysicsForNpc ensures the body stays frozen after the loot panel opens.
- VobMeshCullingDomain: skip destroyed Rigidbodies in StopVobTrackingBasedOnVelocity - VRBackpack: guard null vobLoader in OnItemPutOutOfHolster - AiHandler: guard null waypoint in ReEnableNpc, log warning so bad re-enables are visible
Was a FIXME stub hardcoded to return false, preventing any dead-NPC checks from working (e.g. Thorus couldn't recognize Mordrag as dead after being killed). Now checks Props.BodyState == BsDead, which FightService sets when HP reaches 0. Note: BodyState is runtime-only - a world reload will respawn the NPC alive. A permanent death flag in SaveGame state is needed as a follow-up fix.
…uation After commit 69d7707 correctly fixed the GlobalOther save/restore in ExecutePerception, a stale NPC (e.g. a Shadow guard from a recent perception) remained in GlobalOther between perception calls. This caused two visible bugs: 1. NPCs turned their backs during conversation - routine states like ZS_*_Loop called B_AssessTalk/B_SmartTurnToNpc using 'other', which pointed to the stale Shadow instead of the player. NPCs would physically rotate toward that guard. 2. Important dialogs refused to trigger - conditions like Npc_GetDistToNpc(self,other) or guild attitude checks ran against the Shadow's position/guild, not the hero's, causing them to return false and skip the dialog entirely. Fix in AiHandler: set GlobalOther=GlobalHero before executing ZS_* state functions. This is just a sensible default - perception calls override GlobalOther via their own save/restore, so combat/assess perceptions are unaffected. Fix in DialogService: save/restore GlobalOther and explicitly set it to GlobalHero around Info_*_Condition calls. In Gothic's convention, self=NPC and other=hero in all dialog condition functions.
3a9e778 to
3c57992
Compare
JucanAndreiDaniel
approved these changes
Jun 13, 2026
…VR you had to move it forward by 0.02 :)
JaXt0r
approved these changes
Jun 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stabilizing v2 — NPC AI, Dialog, Inventory & VR fixes
A second round of stabilization fixes targeting NPC perception globals, dialog conditions, inventory checks, font rendering, and VR-specific edge cases.
Changes by file
LiberationSans SDF empty.assetSet atlas mode to Static. In Dynamic mode the empty atlas was being populated with Gothic characters on first render, causing them to render in Liberation Sans instead of the Gothic bitmap font. Static prevents any atlas population so all characters fall through to the
TMP_SpriteAssetset byAbstractMenu/dialogs.StatusBar.prefabFixed HP bar appearing empty in PCVR headsets.
AnimationSystem.csMinor adjustment related to the death/hit animation bypass changes.
AiHandler.csGlobalOther = GlobalHerobefore executing ZS_* state functions. After the perception save/restore was corrected inNpcAiService, a stale NPC (e.g. a Shadow guard) could remain inGlobalOtherbetween perception calls — causing NPCs to turn their backs during conversations and guild/distance dialog conditions to evaluate against the wrong NPC.ReEnableNpc+ warning log for bad re-enables.VobMeshCullingDomain.csSkip destroyed
Rigidbodys inStopVobTrackingBasedOnVelocityto prevent null-ref exceptions during VOB cleanup.NpcHeadMeshBuilder.csNull-safe
NpcLoader/Npcaccess during async head construction — prevents crash when the NPC is destroyed before the build completes.Attack.csStopAllAnimationsbefore playing, so dying always wins regardless of queued actions.OnHitearly-outs if the target is already dead to prevent double damage/animation.AI_Attackis skipped with a log for human NPCs (guild < GIL_SEPERATOR_HUM) — not yet implemented for humans.GoToNpc.csNPCs now stop 1.5 m before the target instead of walking into them.
GoToWp.csFindFastestPathreturningnullno longer crashes — path is guarded before use.VobInitializerDomain.csInitVobCoroutinenow wraps eachInitVobcall in try/catch so a single broken VOB can't halt all lazy loading. Root cause:BIRD/OWL NEAR MARKETreferencedwood_night2which doesn't exist in Gothic'sSfxInst.d, causingGetFirstSound()to return null and silently kill the entire coroutine.SfxModel.csLogs a warning when an SFX symbol is missing from the VM instead of silently returning null from
GetFirstSound.FightService.csCooperates with the death animation bypass — HP reaching 0 sets
BodyState = BsDeadwhichNpc_IsDeadnow reads.NpcAiService.csGlobalOther/GlobalVictimwere incorrectly swapped during restore after perception calls (oldOtherwas assigned fromGlobalVictiminstead ofGlobalOther), causing both globals to be set to the victim. Fixed save/restore order and replaced the fragilenpc2.Id == 0hero check with anIndexcomparison againstGlobalHero.Npc_IsDeadwas aFIXMEstub hardcoded toreturn false. Now checksProps.BodyState == BsDead, fixing e.g. Thorus not recognizing Mordrag as dead after being killed.NpcHelperService.csaiStatefilter was comparingnpcVob.CurrentStateIndex(the calling NPC's own state index) against each candidate, instead of comparing the requestedaiStatevalue.NpcInventoryService.csExtNpcHasItemswas reading fromGetItem/ItemCount(the VOB world-file list), which is never populated by runtimeCreateInvItemscalls. Rewrote to iterate allInvCatsand read fromGetPackedviaGetInventoryItems— the same path used by all other inventory operations. FixesNpc_HasItemsalways returning 0 for the player, breaking dialogs that check inventory (Fisk ore purchase, Bloodwyn extortion, Drax beer, etc.).DialogService.csSave/restore
GlobalOtherand set it toGlobalHeroaroundInfo_*_Conditioncalls, matching Gothic's convention (self= NPC,other= hero in all dialog condition functions). Prevents important dialogs from failing when a stale NPC was left inGlobalOther.FontService.csPre-warms the TMP font atlas on startup by iterating all string-type Daedalus symbols, collecting unique characters, and calling
TryAddCharactersbefore any UI renders. Prevents TMP fallback glyphs on first display of Gothic NPC/item names.VobService.csWorld VOB items have
Amount = 0by Gothic convention (single item = 0). AppliedMathf.Max(1, amount)so items picked up from the floor aren't stored as e.g.ITFOBEERx0in packed inventory.BackPack.prefabCleared placeholder texts (
"x/y","{{CATEGORY}}") that were triggering TMP warnings before boot because they rendered beforeFontServiceset the default sprite asset.VRBackpack.csAdded null guard for
vobLoaderinOnItemPutOutOfHolster. Also applies theMathf.Max(1, amount)fix for picked-up item amounts.VRNpc.csHVR sets
isKinematic = falseon the grabbedRigidbodyduring a grab, causing the NPC corpse to fall or be pushed by the player capsule after releasing.ForceRelease+DisablePhysicsForNpcare now called when opening the loot panel to keep the body frozen.How to test
Npc_HasItemsdialogs (Drax beer, Fisk ore) — they should work correctly.